package ligne2vie.topos.client.panels;

import java.util.ArrayList;
import java.util.List;

import ligne2vie.topos.client.maps.MyMapWidget;
import ligne2vie.topos.shared.bo.TGeorequestResult;

import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.maps.client.geocoder.Geocoder;
import com.google.gwt.maps.client.geocoder.GeocoderCallback;
import com.google.gwt.maps.client.geocoder.GeocoderRequest;
import com.google.gwt.maps.client.geocoder.HasGeocoderResult;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.PushButton;
import com.google.gwt.user.client.ui.TextBox;

/**
 * The class {@link SearchBar} provides a complete widget to
 * 
 * @author thuguerre
 * @since 1.1.0
 */
public class SearchBar extends HorizontalPanel {

	/**
	 * {@link SearchBar}'s singleton instance.
	 */
	private static SearchBar instance = null;

	/**
	 * Width in pixels of the {@link #bOk} widget.
	 */
	private static final int B_OK_WIDTH = 50;

	/**
	 * Side's size in pixels of the geolocate button.
	 */
	private static final int B_GEOLOCATE_SIDE = 48;

	private TextBox tbSearch = new TextBox();
	private Button bOk = new Button("OK");
	private GeolocateButton bGeolocate = new GeolocateButton();

	/**
	 * Default constructor.
	 */
	private SearchBar() {

		this.setVerticalAlignment(HasVerticalAlignment.ALIGN_BOTTOM);

		this.add(tbSearch);
		this.add(bOk);
		this.add(bGeolocate);

		this.tbSearch.setStylePrimaryName("searchBarTb");
		this.tbSearch.addKeyPressHandler(new EnterKeyPressHandler(bOk));

		this.bOk.addClickHandler(new BOkClickHandler(this.tbSearch));
	}

	/**
	 * @return {@link SearchBar}'s singleton instance.
	 */
	public static SearchBar getInstance() {

		if (instance == null) {
			instance = new SearchBar();
		}

		return instance;
	}

	/**
	 * Sets the focus on the Text Box Search, allowing user writing directly its search.
	 */
	public void setFocus() {
		this.tbSearch.setFocus(true);
	}

	/**
	 * Defines the height of the current search bar and all its components.
	 * 
	 * @param pixels
	 *            Height in pixels.
	 */
	public void setHeight(int pixels) {

		super.setHeight(pixels + "px");

		this.bOk.setHeight(pixels / 2 + 7 + "px");
		this.bGeolocate.setHeight(B_GEOLOCATE_SIDE + "px");
		this.tbSearch.setHeight(pixels / 2 + "px");
	}

	/**
	 * Defines the width of the current search bar and all its components.
	 * 
	 * @param pixels
	 *            Width in pixels.
	 */
	public void setWidth(int pixels) {

		super.setWidth(pixels + "px");

		this.bOk.setWidth(B_OK_WIDTH + "px");
		this.bGeolocate.setWidth(B_GEOLOCATE_SIDE + "px");
		this.tbSearch.setWidth(pixels - (B_OK_WIDTH + 10) - (B_GEOLOCATE_SIDE + 10) + "px");
	}

	/**
	 * The class {@link EnterKeyPressHandler} allows reacting on the Enter key press event, in order to perform the
	 * button OK click.
	 * 
	 * @author thuguerre
	 * @since 1.1.0
	 */
	private class EnterKeyPressHandler implements KeyPressHandler {

		private Button bOk;

		/**
		 * Default constructor.
		 * 
		 * @param bOk
		 *            Button to click on enter.
		 */
		public EnterKeyPressHandler(Button bOk) {
			this.bOk = bOk;
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void onKeyPress(KeyPressEvent event) {

			if (event.getCharCode() == 13) {
				this.bOk.click();
			}
		}
	}

	/**
	 * The class {@link BOkClickHandler} allows performing the map search and centering regarding the address or
	 * remarkable map point which has been written into {@link SearchBar#tbSearch}.
	 * 
	 * @author thuguerre
	 * @since 1.1.0
	 */
	private class BOkClickHandler implements ClickHandler {

		private TextBox tbSearch = null;

		/**
		 * Default constructor.
		 */
		public BOkClickHandler(TextBox tbSearch) {
			this.tbSearch = tbSearch;
		}

		@Override
		public void onClick(ClickEvent event) {

			GeocoderRequest request = new GeocoderRequest();
			request.setAddress(this.tbSearch.getText());

			Geocoder geocoder = new Geocoder();
			geocoder.geocode(request, new GeocoderResponse());
		}
	}

	/**
	 * The class {@link GeocoderResponse} allows retrieving and displaying results of a {@link GeocoderRequest}.
	 * <p>
	 * If only one result is found, the map is center on it directly. If several results are found, a warning message is
	 * displayed, asking the user to make a more precise request.
	 * 
	 * @author thuguerre
	 * @since 1.1.0
	 */
	private class GeocoderResponse extends GeocoderCallback {

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void callback(List<HasGeocoderResult> responses, String status) {

			if (responses != null) {

				int resultIndex = 0;
				List<TGeorequestResult> geoResults = new ArrayList<TGeorequestResult>();

				for (HasGeocoderResult response : responses) {

					if (responses.size() == 1) {

						// --- setting result index to -1 in order to tell TGeorequestResult it is the only result
						geoResults.add(new TGeorequestResult(-1, response.getGeometry().getLocation().getLatitude(),
								response.getGeometry().getLocation().getLongitude(), response.getAddressComponents()));

					} else {

						geoResults.add(new TGeorequestResult(resultIndex++, response.getGeometry().getLocation()
								.getLatitude(), response.getGeometry().getLocation().getLongitude(), response
								.getAddressComponents()));
					}

				}

				MyMapWidget.getInstance().addGeorequestResults(geoResults);
			}
		}
	}

	/**
	 * The class {@link GeolocateButton} provides a clickable button allowing user to center map on its current
	 * geographic position.
	 * 
	 * @author thuguerre
	 * @since 1.1.0
	 */
	private class GeolocateButton extends PushButton implements ClickHandler {

		/**
		 * Default constructor.
		 */
		public GeolocateButton() {
			super(new Image("/img/geolocate/geolocate2.png"));
			this.addClickHandler(this);
			this.setTitle("Centrer la carte sur ma position actuelle");
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void onClick(ClickEvent event) {
			MyMapWidget.getInstance().geolocateUser();
		}
	}
}
